/*
 *    Copyright (C) 2007-2009 Aike J Sommer
 *
 *
 *    This program is free software: you can
 *    redistribute it and/or modify it under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation, either version 3 of the License, or (at your
 *    option) any later version.
 *
 *    This program is distributed in the hope
 *    that it will be useful, but WITHOUT ANY WARRANTY; without even
 *    the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *    PARTICULAR PURPOSE.  See the GNU General Public License for
 *    more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see
 *    <http://www.gnu.org/licenses/>.
 *
 *
 *    You can reach the author and get more information about this
 *    project at: http://aikesommer.name/ or
 *    http://code.google.com/p/viewncontrol
 */
package name.aikesommer.viewncontrol.web;

import de.divis_hagen.json.JSONContext;
import de.divis_hagen.json.JSONException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import name.aikesommer.viewncontrol.protocol.SliceStore;
import name.aikesommer.viewncontrol.protocol.VNCException;
import name.aikesommer.viewncontrol.web.control.ScreenViewController;
import name.aikesommer.viewncontrol.web.xml.CursorUpdate;
import name.aikesommer.viewncontrol.web.xml.ScreenSlice;
import name.aikesommer.viewncontrol.web.xml.ScreenSlices;

/**
 *
 * @author Aike J Sommer
 */
public class ScreenViewServlet extends HttpServlet {

    public static enum ResultType {

        XML, JSON
    }

    public static enum Mode {

        CheckForSlices, FetchSlice, CheckForCursorUpdates, UpdateCursor, UpdateKey
    }

    public ScreenViewServlet() {
    }

    /** 
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        PrintWriter out = null;
        try {
            ScreenViewController controller = ScreenViewController.self(request.getSession());

            String modeParam = request.getParameter("mode");
            Mode mode = Mode.valueOf(
                    modeParam);
            switch (mode) {
                case CheckForSlices: {
                    long since = Long.parseLong(
                            request.getParameter("since"));
                    boolean fullUpdate = Boolean.parseBoolean(request.getParameter("full-update"));

                    out = response.getWriter();
                    Map<Long, SliceStore.Slice> slices;
                    try {
                        if (fullUpdate) {
                            controller.purgeAll();
                        }
                        slices = controller.getSlices(since, 30000);
                        if (!fullUpdate && slices.size() > 50) {
                            controller.purgeAll();
                            slices = controller.getSlices(since, 10000);
                        }
                    } catch (VNCException.ConnectionLost ex) {
//                            Log.debugS("lost vnc connection");
//                            Log.debugS(ex);
                        response.setContentType("text/plain;charset=UTF-8");
                        out.println("connection lost");
                        return;
                    }

                    ScreenSlices result = new ScreenSlices();
                    int i = 0;
                    for (Map.Entry<Long, SliceStore.Slice> slice : slices.entrySet()) {
                        String debugInfo = createDebugInfo(
                                slice.getValue().info);
                        result.getSlices().add(new ScreenSlice(
                                slice.getKey(), slice.getValue().x, slice.getValue().y, slice.
                                getValue().width, slice.getValue().height, debugInfo));
                        if (++i >= 10) {
                            break;
                        }
                    }

                    String resultTypeParam = request.getParameter(
                            "result-type");
                    ResultType resultType = resultTypeParam == null ? ResultType.XML
                            : ResultType.valueOf(resultTypeParam);
                    switch (resultType) {
                        case XML:
                            response.setContentType("text/xml;charset=UTF-8");
                            try {
                                JAXBContext context = JAXBContext.newInstance(ScreenSlices.class);
                                Marshaller marshaller = context.createMarshaller();
                                marshaller.setProperty(
                                        Marshaller.JAXB_FORMATTED_OUTPUT,
                                        true);
                                marshaller.marshal(result, out);
                            } catch (JAXBException ex) {
                                throw new RuntimeException(ex);
                            }
                            break;
                        case JSON:
                            response.setContentType(
                                    "text/json;charset=UTF-8");
                            try {
                                JSONContext context = JSONContext.newInstance(ScreenSlices.class);
                                de.divis_hagen.json.Marshaller marshaller =
                                        context.createMarshaller();
                                marshaller.marshall(result, out);
                            } catch (JSONException ex) {
                                throw new RuntimeException(ex);
                            }
                            break;
                        default:
                            throw new IllegalArgumentException("result type: " + resultTypeParam);
                    }
                }
                break;
                case CheckForCursorUpdates: {
                    long since = Long.parseLong(
                            request.getParameter("since"));

                    out = response.getWriter();
                    SliceStore.Slice slice = null;
                    try {
                        slice = controller.getCursorUpdate(since, 30000);
                    } catch (VNCException.ConnectionLost ex) {
//                            Log.debugS("lost vnc connection");
//                            Log.debugS(ex);
                        response.setContentType("text/plain;charset=UTF-8");
                        out.println("connection lost");
                        return;
                    }

                    CursorUpdate result;
                    if (slice == null) {
                        result = new CursorUpdate();
                    } else {
                        String debugInfo = createDebugInfo(slice.info);
                        result = new CursorUpdate(new ScreenSlice(
                                (Long) slice.info.get("slice-id"), slice.x,
                                slice.y, slice.width, slice.height,
                                debugInfo), slice.hasData());
                    }

                    String resultTypeParam = request.getParameter(
                            "result-type");
                    ResultType resultType = resultTypeParam == null ? ResultType.XML
                            : ResultType.valueOf(resultTypeParam);
                    switch (resultType) {
                        case XML:
                            response.setContentType("text/xml;charset=UTF-8");
                            try {
                                JAXBContext context = JAXBContext.newInstance(CursorUpdate.class);
                                Marshaller marshaller = context.createMarshaller();
                                marshaller.setProperty(
                                        Marshaller.JAXB_FORMATTED_OUTPUT,
                                        true);
                                marshaller.marshal(result, out);
                            } catch (JAXBException ex) {
                                throw new RuntimeException(ex);
                            }
                            break;
                        case JSON:
                            response.setContentType(
                                    "text/json;charset=UTF-8");
                            try {
                                JSONContext context = JSONContext.newInstance(CursorUpdate.class);
                                de.divis_hagen.json.Marshaller marshaller =
                                        context.createMarshaller();
                                marshaller.marshall(result, out);
                            } catch (JSONException ex) {
                                throw new RuntimeException(ex);
                            }
                            break;
                        default:
                            throw new IllegalArgumentException("result type: " + resultTypeParam);
                    }
                }
                break;
                case FetchSlice: {
                    long id = Long.parseLong(request.getParameter("id"));
                    try {
                        SliceStore.Slice slice = controller.fetchSlice(id);
                        response.setContentType(slice.type().mimetype());
                        response.getOutputStream().write(slice.data());
                    } catch (NoSuchElementException ex) {
                        response.sendError(response.SC_NOT_FOUND);
                    }
                    response.getOutputStream().close();
                    controller.purgeSlice(id);
                }
                break;
                case UpdateCursor: {
                    String xParam = request.getParameter("x");
                    String yParam = request.getParameter("y");
                    String pressedParam = request.getParameter("pressed");
                    String releasedParam = request.getParameter("released");

                    if (xParam != null && yParam != null) {
                        int x = Integer.parseInt(xParam);
                        int y = Integer.parseInt(yParam);
                        try {
                            controller.cursorMoved(x, y);
                        } catch (VNCException.ConnectionLost ex) {
                            response.sendError(response.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
                        }
                    } else {
                        int button;
                        boolean pressed;
                        if (pressedParam != null) {
                            pressed = true;
                            button = Integer.parseInt(pressedParam);
                        } else {
                            pressed = false;
                            button = Integer.parseInt(releasedParam);
                        }
                        try {
                            controller.buttonChanged(button, pressed);
                        } catch (VNCException.ConnectionLost ex) {
                            response.sendError(response.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
                        }
                    }
                    response.getOutputStream().close();
                }
                break;
                case UpdateKey: {
                    String pressedParam = request.getParameter("pressed");
//                    String releasedParam = request.getParameter("released");

                    int key = -1;
//                    boolean pressed;
                    if (pressedParam != null) {
//                        pressed = true;
                        key = Integer.parseInt(pressedParam);
//                    } else {
//                        pressed = false;
//                        key = Integer.parseInt(releasedParam);
                    }
                    if (key >= 0) {
                        try {
                            controller.keyPressed(key);
                        } catch (VNCException.ConnectionLost ex) {
                            response.sendError(response.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
                        }
                    }

                    response.getOutputStream().close();
                }
                break;
                default:
                    throw new IllegalArgumentException("mode: " + modeParam);
            }
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private String createDebugInfo(Map<String, Object> info) {
        StringBuffer result = null;
        for (Map.Entry<String, Object> entry : info.entrySet()) {
            String value =
                    entry.getKey() + ": ''" + entry.getValue().toString() + "''";
            if (result == null) {
                result = new StringBuffer(value);
            } else {
                result.append("; ");
                result.append(value);
            }
        }
        return result == null ? "" : result.toString();
    }

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>
}
